﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;

namespace FreeDatabase
{
    class Client
    {
        public string UserName { get; set; }
        public string HostName { get; set; }
        public string ClientName { get; set; }
        public string Version { get; set; }

        public Uri ServerAddress { get; set; }

        public Client()
        {
            UserName = "user";
            HostName = "host";
            ClientName = "client";
            Version = "1";

            ServerAddress = new Uri("http://freedb.freedb.org/~cddb/cddb.cgi");
        }

        private string Send(string command)
        {
            string value = string.Format("cmd={0}&hello={1}+{2}+{3}+{4}&proto=6", command,
                UserName, HostName, ClientName, Version);

            string result;
            using (var client = new WebClient())
            {
                client.Encoding = Encoding.UTF8;
                result = client.UploadString(ServerAddress, value);
            }
            return result;
        }

        public QueryResult Query(AudioDisc audio)
        {
            var tracks = audio.Tracks;
            string command = string.Format("cddb+query+{0}+{1}+{2}+{3}", GetDiscID(tracks),
                tracks.Count, string.Join("+", tracks.Select(e => e.Index)), GetDuration(tracks));
            string response = Send(command);

            if (response == null || response.Length < 3)
            {
                throw new Exception();
            }

            int code;
            var items = new List<QueryResultMatch>();

            using (var reader = new StringReader(response))
            {
                string value = reader.ReadLine();
                if (int.TryParse(value.Substring(0, 3), out code))
                {
                    if (code == 200)
                    {
                        string[] array = value.Split(new char[] { ' ' }, 4);
                        if (array.Length == 4)
                        {
                            items.Add(new QueryResultMatch(array[1], array[2]));
                        }
                    }
                    else
                    {
                        if (code == 210 | code == 211)
                        {
                            while ((value = reader.ReadLine()) != null)
                            {
                                if (value.StartsWith("."))
                                {
                                    break;
                                }
                                string[] array = value.Split(new char[] { ' ' }, 3);
                                if (array.Length == 3)
                                {
                                    items.Add(new QueryResultMatch(array[0], array[1]));
                                }
                            }
                        }
                    }
                }
            }

            return new QueryResult(code, items.ToArray());
        }

        public ReadResult Read(string category, string id)
        {
            string command = string.Format("cddb+read+{0}+{1}", category, id);
            string response = Send(command);

            if (response == null || response.Length < 3)
            {
                throw new Exception();
            }

            int code;
            string title = "";
            var items = new Dictionary<string, string>();

            using (var reader = new StringReader(response))
            {
                string value = reader.ReadLine();
                if (int.TryParse(value.Substring(0, 3), out code))
                {
                    if (code == 210)
                    {
                        while ((value = reader.ReadLine()) != null)
                        {
                            if (value.StartsWith("."))
                            {
                                break;
                            }
                            if (value.StartsWith("#"))
                            {
                                continue;
                            }
                            string[] array = value.Split(new char[] { '=' }, 2);
                            if (array.Length == 2)
                            {
                                if (value.StartsWith("DTITLE"))
                                {
                                    title = title + array[1];
                                }
                                else
                                {
                                    if (value.StartsWith("TTITLE"))
                                    {
                                        if (items.ContainsKey(array[0]))
                                        {
                                            items[array[0]] = items[array[0]] + array[1];
                                        }
                                        else
                                        {
                                            items[array[0]] = array[1];
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }

            return new ReadResult(code, title, items.Select(e => e.Value).ToArray());
        }

        private string GetDiscID(IList<Track> items)
        {
            int result = 0;

            if (items.Count > 0)
            {
                var array = items.Select(e => e.Start).ToArray();

                int value = 0;
                for (int i = 0; i < array.Length; i++)
                {
                    int j = (array[i].Minutes * 60) + array[i].Seconds;
                    while (j > 0)
                    {
                        value = value + (j % 10);
                        j = j / 10;
                    }
                }

                result = (value % 255) << 24 | (GetDuration(items) - ((array[0].Minutes * 60) +
                    array[0].Seconds)) << 8 | items.Count;
            }

            return result.ToString("x8");
        }

        private int GetDuration(IList<Track> items)
        {
            int result = 0;

            if (items.Count > 0)
            {
                var item = items.Last();
                result = (item.Index + item.Length) / AudioDisc.FramesPerSecond;
            }

            return result;
        }
    }

    class QueryResult
    {
        private int code;
        private QueryResultMatch[] matches;

        public QueryResult(int code, QueryResultMatch[] matches)
        {
            this.code = code;
            this.matches = matches;
        }

        public int Code
        {
            get
            {
                return code;
            }
        }
        public QueryResultMatch[] Matches
        {
            get
            {
                return matches;
            }
        }
    }

    class QueryResultMatch
    {
        private string category;
        private string id;

        public QueryResultMatch(string category, string id)
        {
            this.category = category;
            this.id = id;
        }

        public string Category
        {
            get
            {
                return category;
            }
        }

        public string ID
        {
            get
            {
                return id;
            }
        }
    }

    class ReadResult
    {
        private int code;
        private string title;
        private string[] tracks;

        public ReadResult(int code, string title, string[] tracks)
        {
            this.code = code;
            this.title = title;
            this.tracks = tracks;
        }

        public int Code
        {
            get
            {
                return code;
            }
        }
        public string Title
        {
            get
            {
                return title;
            }
        }
        public string[] Tracks
        {
            get
            {
                return tracks;
            }
        }
    }
}
